import wsgiref.handlers
import logging
import pickle
import cPickle
from google.appengine.api import users
from google.appengine.api import memcache
from google.appengine.api import mail
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db
import os
import re
import oauth
from django.utils import simplejson
from google.appengine.ext.webapp import template
import sys
import jsmin
import base64
import hashlib
import urllib
import traceback
#sys.path.append('pycrypto-2.0.1')
from Crypto.PublicKey import RSA
from Crypto.Util import number

def sort_setlist(a,b):
    a_title = a[1].lower()
    b_title = b[1].lower()
    if (a_title < b_title):
	return -1
    elif (a_title == b_title):
	return 0
    else:
	return 1

# We must determine if the user has access right to the resource he/she trying to get.
# As with most systems, this involves two parts:
# a.  authentication (is this user really who he say he is?)
# b.  authorization (does this user have access to the resource?)
# This class only deal with authentication.  Authorization must be done much later (when
# we know what the resource is).  We can potentially use template system to do authentication
# but for now we will handle authentication this way.
class Page(webapp.RequestHandler):
    applicationName = 'AstoundLearning'
    baseURL = ''
    host = ''
    jsWithoutSocial = ''
    combinedCSS = ''
    template_values = {}
    social = None
    def handle_exception(self,exception,debug_mode):
	lines = ''.join(traceback.format_exception(*sys.exc_info()))
	logging.error(lines)
	self.response.out.write(self.applicationName + ' apologizes for your inconvenience.  An error happened, and we will investigate it.  Please try again later.')

    def get(self):
	self.process()

    def post(self):
	self.process()

    def deleteCache(self,ck):
	memcache.delete(ck)

    def getCache(self,ck):
	instance = memcache.get(ck)
	if instance is not None:
	    instance = pickle.loads(instance)
	else:
	    instance = db.get(ck)
	    memcache.set(ck,pickle.dumps(instance))
	return instance
    def isSocial(self):
	return self.social

    def get_current_user(self):
	if (self._isValidSignature()):
	    self.social = True
	    viewerID = self.request.get('opensocial_viewer_id')
	    consumerKey = self.request.get('oauth_consumer_key')
	    user = CustomUser.gql("WHERE viewerID = :viewerID AND consumerKey = :consumerKey", viewerID = viewerID, consumerKey = consumerKey).get()
	    if (not user):
		user = CustomUser(isSocial = True, viewerID = viewerID, consumerKey = consumerKey);
		user.put()
	    return user
	else:
	    self.social = False
	    user = users.get_current_user();
	    if (not user):
		return None
	    user = CustomUser.gql("WHERE googleAccount = :googleAccount", googleAccount = users.get_current_user()).get()
	    if (not user):
		user = users.get_current_user()
		if (not user):
		    return None
		nickname = user.nickname()
		email = user.email()
		if (nickname == email):
		    nickname = ''
		user = CustomUser(googleAccount = user, name = nickname, email = email)
		user.put()
	    return user
    def convertGoogleAccountToCustomUser(self,googleAccount):
	user = CustomUser.gql("WHERE googleAccount = :googleAccount", googleAccount = googleAccount).get()
	if (not user):
	    user = CustomUser(googleAccount = googleAccount)
	    user.put()
	return user
    def _isValidSignature(self):
	# oauth_consumer_key parameter indicates the container from which the request is being sent.
	# oauth_signature_method indicates the method used to sign the request
	# For RSA-SHA1, xoauth_signature_publickey contains the name of the public key used to sign the request
	# For HMAC-SHA1, you will need to establish a secret key between you application and the container.
	# For RSA-SHA1, you will need to obtain the certificate referenced by xoauth_signature_publickey.  See
	# https://opensocialresources.appspot.com/certificates
	# Certificates should not be fetched each time you want to validate parameters.  Implements a server side
	# key cache indexed on the value of xoauth_signature_publickey, oauth_consumer_key, and oauth_signature_method
	# Construct a RSA.public object
	consumerKey = self.request.get('oauth_consumer_key')
	signatureMethod = self.request.get('oauth_signature_method')
	if (not signatureMethod) or (signatureMethod == ''):
	    return False

	if (signatureMethod == 'RSA-SHA1'):
	    exponent = 65537
	    publicKeys = {}
	    publicKeys['www.google.com']  = """0x00d0515eee9087c88b16e890738d18c5bdf9e77413d5f89bdf48f2ea4f429de202da88bd6b3b5c26c06c6ab3407d6a5fd634d21ad0e514508fc388ded46242cfca7f319639dbcba48939a17a5d4f9f2d838165621e5f6e1228568567e06bed4a32a6245b2833c351b442472f569677ef9d5f39108c4b0d7015f042f7c36f46276d"""
	    publicKeys['orkut.com'] = """0x00b1e057678343866db89d7dec251899261bf2f5e0d95f5d868f81d600c9a101c9e6da20606290228308551ed3acf9921421dcd01ef1de35dd3275cd4983c7be0be325ce8dfc3af6860f7ab0bf32742cd9fb2fcd1cd1756bbc400b743f73acefb45d26694caf4f26b9765b9f65665245524de957e8c547c358781fdfb68ec056d1"""
	    publicKeys['hi5.com'] = """0x009981dad8b0409e18efd8b2b0df68efef23232aefe0d87409889b3c20df9c27b62dc3af707a912e8e791881008513bdd6575f75e2b491715650c51ff3db4629a6d1ddde42f49f5b7fb4c48845f42dc4f50163e8b581cfa6c1961e69d29497ca0019f405d1d4e30cda50d147d870429d58563cc99378585b6a83e7dd5ab91390bf"""
	    public_key_str = publicKeys[consumerKey]
	    public_key_long = long(public_key_str, 16)
	    public_key = RSA.construct((public_key_long, exponent))

	    # Rebuild the message host locally
	    oauth_request = oauth.OAuthRequest(http_method = self.request.method,
		    http_url=self.request.url,
		    parameters=self.request.params.mixed())
	    message = '&'.join((oauth.escape(oauth_request.get_normalized_http_method()),
		oauth.escape(oauth_request.get_normalized_http_url()),
		oauth.escape(oauth_request.get_normalized_parameters()),))
	    local_hash = hashlib.sha1(message).digest()

	    # Apply the public key to the signature from the remote host
	    sig = base64.decodestring(urllib.unquote(self.request.params.mixed()["oauth_signature"]))
	    remote_hash = public_key.encrypt(sig,'')[0][-20:]

	    # Verify that the locally-built value matches the value from the remote server.
	    if local_hash == remote_hash:
		return True
	    else:
		logging.info('Either oauth_consumer_key: ' + consumerKey + ' does not exist in publicKeys or the container changed its public key' + "\n")
		return False
	elif (signatureMethod == 'HMAC-SHA1'):
	    logging.info('Unsupported oauth_signature_method ' + signatureMethod + ', oauth_consumer_key: ' + consumerKey + "\n");
	    return False
	else:
	    logging.info('Unsupported oauth_signature_method ' + signatureMethod + ', oauth_consumer_key: ' + consumerKey + "\n");
	    return False

    def combineTemplates(self):
	templates = ['addEntryTemplate','listViewRowTemplate_contributor','listViewTemplate']
	templates.append('editEntryTemplate')
	templates.append('listViewTemplate_contributor')
	templates.append('studyTableTemplate')
	templates.append('addEntryTemplate')
	templates.append('contact')
	templates.append('faq')
	templates.append('ourFeedback')
	templates.append('tos')
	templates_json = {}
	for t in templates:
	    filename = os.path.join(os.path.dirname(__file__), 'templates/ajax/' + t + '.html')
	    templates_json[t] = template.render(filename,self.template_values)
	return simplejson.dumps(templates_json)
    def combineCSS(self):
	base = os.path.dirname(__file__)
	css = file(os.path.join(base,'css/themes/flora/flora.tabs.css')).read() + "\n"
	css = css + file(os.path.join(base,'css/themes/flora/flora.css')).read()
	return css
    def combineJavascriptWithoutSocial(self):
	base = os.path.dirname(__file__)
	js = file(os.path.join(base,'javascript/jquery-1.2.6.min.js')).read();
	js = js + file(os.path.join(base,'javascript/jquery.ui-1.5.2/ui/minified/jquery.ui.all.min.js')).read() + "\n\n"
	#js = js + file(os.path.join(base,'tinymce/jscripts/tiny_mce/tiny_mce.js')).read() + "\n\n"
	if (False):
	    t = file(os.path.join(base,'javascript/plugins4jquery/blockUI.js')).read() + "\n\n"
	    t = t + file(os.path.join(base,'javascript/jquery.scrollIntoView.js')).read() + "\n\n"
	    t = t + file(os.path.join(base,'javascript/our.js')).read() + "\n\n"
	    js = js + jsmin.jsmin(t)
	else:
	    js = js + file(os.path.join(base,'javascript/plugins4jquery/blockUI.js')).read() + "\n\n"
	    js = js + file(os.path.join(base,'javascript/jquery.scrollIntoView.js')).read() + "\n\n"
	    js = js + file(os.path.join(base,'javascript/our.js')).read() + "\n\n"
	js = js + 'study.templates = ' + self.combineTemplates() + ';' + "\n\n"
	return js
    def process(self):
	path = self.request.path
	tuple = '/css/', '/javascript/', '/tinymce/'
	if (path.startswith(tuple)):
	    base = os.path.dirname(__file__)
	    path = path.lstrip('/')
	    content = file(os.path.join(base,path)).read()
	    self.response.headers['Cache-Control'] = 'public, max-age=600'
	    if (path.endswith('.css')):
		self.response.headers['Content-Type'] = 'text/css'
	    elif (path.endswith('.htm')) or (path.endswith('.html')):
		self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
	    else:
		self.response.headers['Content-Type'] = 'text/javascript'
	    self.response.out.write(content)
	    return;

	self.social = None
	user = self.get_current_user()
	if (self._isValidSignature()):
	    self.response.headers['Connection'] = 'close'

        if (self.jsWithoutSocial == ''):
	    self.jsWithoutSocial = self.combineJavascriptWithoutSocial();
	    self.combinedCSS = self.combineCSS()

	# self.redirect(users.create_login_url(self.request.uri))
	if (user):
	    preferences = Preferences.gql("WHERE user = :user", user = user).get()
	    if (not preferences):
		preferences = Preferences(user = user)
	    preferences.put()

	template_file = 'templates' + path
	if (re.search('\/$',template_file)):
            template_file = template_file + 'index.html'		
	template_values = {
            'logout_url': users.create_logout_url(self.request.uri),
            'login_url': users.create_login_url(self.request.uri),
	    'user': self.get_current_user(),
	    'applicationName': self.applicationName,
	    'javascript' : self.jsWithoutSocial,
	    'combinedCSS': self.combinedCSS
        }
	url = self.request.url;
	url = re.sub(r'\?.*','',url)
	url = re.sub(path,'',url)
	self.baseURL = template_values['baseURL'] = 'http:' + '//' + os.environ['HTTP_HOST']
	template_values['host'] = self.host = os.environ['HTTP_HOST']
	self.template_values = template_values;
	UnAuthenticatedPages = ('/login.html', '/contactus.html')
	if (path in UnAuthenticatedPages):
	    pass
        elif ((path == '/') and (user)):
	    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user", user = user)
	    cs_sets = []
	    for cs in currentSubscription:
		cs_sets.append(cs.set)
	    template_values['sets'] = cs_sets
	    template_values['templates'] = self.combineTemplates()
	elif ((path == '/') and (not user)):
	    template_values['templates'] = self.combineTemplates()
	elif (path == '/ajax/templates.json'):
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(self.combineTemplates())
	    return;
	elif (path == '/ajax/addsetform.html'):
	    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user", user = user)
	    cs_sets = []
	    for cs in currentSubscription:
		cs_sets.append(cs.set)
	    template_values['sets'] = cs_sets
	    if (user.getEmail() is None):
		template_values['askForEmail'] = True
	elif (path == '/ajax/addset.json'):
	    title = self.request.get('title')
	    materials = self.request.get('materials')
	    visibility = self.request.get('visibility')
	    description = self.request.get('description')
	    set = StudySet.gql("WHERE author = :author and title = :title", author = user, title = title).get()
	    if (not set ):
		set = StudySet(author = self.get_current_user(), title = title, visibility = visibility, studyMaterials = materials)
		set.description = description
		set.entryCount = 0
		set.subscriptionCount = 1
		set.put()
	    set.subscribe(user)
	    set.makeThisSetCurrentForUser(user)
	    email = self.request.get('email')
	    if (email):
		user.email = email;
		self.deleteCache(str(user.key()))
		user.put();
            return_data = {}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/editSet.html'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['title'] = set.title
	    template_values['setKey'] = setKey
	    option_list = '';
	    for v in ['public', 'private']:
		option_list = option_list + '<option value="' + v + '"'
		if (v == set.visibility):
		    option_list = option_list + ' selected'
		option_list = option_list + '>' + v + '</option>'
	    template_values['option_list'] = option_list
	    template_values['materials'] = set.studyMaterials
	    template_values['description'] = set.description
	    if (user.getEmail() is None):
		template_values['askForEmail'] = True
	elif (path == '/ajax/saveSet.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    set.title = self.request.get('title')
	    set.visibility = self.request.get('visibility')
	    set.studyMaterials = self.request.get('materials')
	    set.description = self.request.get('description')
	    set.deleteCache(all = False)
	    set.put()
            return_data = {}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    email = self.request.get('email')
	    if (email):
		user.email = email;
		self.deleteCache(str(user.key()))
		user.put();
	    return;
	elif (path == '/ajax/ourFeedback.html'):
	    template_values['promptForEmail'] = False
	    sender = self.get_current_user().getEmail()
	    if not sender:
		template_values['promptForEmail'] = True
	elif (path == '/ajax/sendOurFeedback.json'):
	    ourFeedback = self.request.get('ourFeedback')
	    sender = self.get_current_user().getEmail()
	    if not sender:
		sender = self.request.get('email')
		if sender:
		    user.email = sender;
		    self.deleteCache(str(user.key()))
		    user.put();
		else:
		    sender = 'khaitdoan@gmail.com'
	    to = 'khaitdoan@gmail.com'
	    mail.send_mail(sender = sender, to = to, subject = self.applicationName + ' Feedback', body = ourFeedback, html = ourFeedback)
	    return_data = {}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data);
	    return;
	elif (path == '/ajax/sendFeedback.json'):
	    feedback = self.request.get('feedback')
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    sender = self.get_current_user().getEmail()
	    if not sender:
		sender = self.request.get('email')
		if sender:
		    user.email = sender;
		    self.deleteCache(str(user.key()))
		    user.put();
	    subject = self.applicationName + ' - Feedback on ' + set.title
	    to = set.getContributorEmails()
	    return_data = {}
	    return_data['status'] = 'ok'
	    if (sender) and (to):
		mail.send_mail(sender = sender, to = to, subject = subject, body = feedback, html = feedback)
	    else:
		return_data['status'] = 'failed'
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data);
	    return;
	elif (path == '/ajax/feedback.html'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    if (set.getContributorEmails() is None):
		template_values['noContributorEmails'] = True
	    template_values['contributor'] = set.isContributor(user)
	    template_values['promptForEmail'] = False
	    sender = self.get_current_user().getEmail()
	    if not sender:
		template_values['promptForEmail'] = True
	elif (path == '/ajax/studyMaterials.html'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['materials'] = set.studyMaterials or "Author / Contributors of this set did not recommend any study materials."
	elif (path == '/ajax/renderSet.json'):
	    return_data = {}
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['title'] = set.title
	    template_values['setKey'] = setKey
	    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = set).get();
	    template_values['subscribed'] = False
	    template_values['contributor'] = set.isContributor(user)
	    if currentSubscription:
                template_values['subscribed'] = True						      
	    set.makeThisSetCurrentForUser(user)
	    template_file = os.path.join(os.path.dirname(__file__), 'templates/ajax/renderSet.html')
	    return_data['renderSetHTML'] = template.render(template_file, template_values)
	    tags = set.tags()
	    tags.insert(0,'');
	    return_data['tags'] = tags;
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/renderSet.html'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['title'] = set.title
	    template_values['setKey'] = setKey
	    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = set).get();
	    template_values['subscribed'] = False
	    template_values['contributor'] = set.isContributor(user)
	    if currentSubscription:
                template_values['subscribed'] = True						      
	    set.makeThisSetCurrentForUser(user)
	elif (path == '/ajax/listentries.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
            return_data = {'setKey': setKey}
	    return_data['contributor'] = set.isContributor(user)
	    entries = []
            results = SetEntry.gql("WHERE set = :set", set = set.key())	    
	    for r in results:
		entry = {}
		entry['question'] = r.question
		entry['answer'] = r.answer
		entry['tags'] = ','.join(r.tags)
		entries.append(entry)
	    return_data['entries'] = entries
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
        elif (path == '/ajax/study.json'):
            setKey = self.request.get('setKey')
	    tag = self.request.get('tag');
	    set = self.getCache(setKey)
	    seen = {}
	    contributor = set.isContributor(user);
	    ordered_list = []
	    if (tag != ''):
		entries = memcache.get(tag + str(set.key()));
		if entries is not None:
		    entries = pickle.loads(entries)
		else:
		    entries = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = tag)
		    memcache.set(tag + str(set.key()), pickle.dumps(entries))
		    entries = memcache.get(tag + str(set.key()))
		    entries = pickle.loads(entries)
	    else:
		entries = memcache.get('_entries' + str(set.key()));
		if entries is not None:
		    entries = pickle.loads(entries)
		else:
		    entries = SetEntry.gql("WHERE set = :set", set = set)
		    memcache.set('_entries' + str(set.key()), pickle.dumps(entries))
		    entries = memcache.get('_entries' + str(set.key()))
		    entries = pickle.loads(entries)
	    for entry in entries:
		key_as_str = str(entry.key())
		if (key_as_str not in seen):
		    data = {}
		    data['shown'] = 0
		    data['wrong'] = 0
		    data['entryKey'] = key_as_str
		    data['setKey'] = setKey
		    ordered_list.append(data)
	    return_data = {'entries': ordered_list, 'contributor': contributor}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/processShowAnswer.json'):
	    entryKey = self.request.get('entryKey')
	    setKey = self.request.get('setKey')
	    entry = self.getCache(entryKey)
	    result = StudyResult.gql("WHERE user = :user AND entry = :entry", user = user, entry = entry).get();
	    if (not result):
                set = self.getCache(setKey)
		entry = self.getCache(entryKey)
		result = StudyResult(user = user, set = set, entry = entry)
	    result.shown = int(self.request.get('shown'))
	    result.wrong = int(self.request.get('wrong'))
	    result.ratio = float(result.wrong / result.shown)
	    result.put();
	    return_data = simplejson.dumps({})
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/study.html'):
            setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['title'] = set.title
	    template_values['setKey'] = setKey
	    tags = set.tags()
	    taglist = '<option value="">' + 'Please select a tag.' + '</option>';
	    for t in tags:
		taglist = taglist + '<option value="' + t + '">' + t + '</option>'
	    template_values['taglist'] = taglist
	elif (path == '/ajax/addentry.json'):
            action = self.request.get('action')
	    setKey = self.request.get('setKey')
	    question = self.request.get('question')
            answer = self.request.get('answer')						   
	    tags = self.request.get_all('tags')
	    if (self.isSocial()):
		tags = tags.pop(0)
		temp = tags.split(',')
		tags = []
		for t in temp:
		    x = urllib.unquote(t)
		    tags.append(x)
	    set = self.getCache(setKey)
	    tags_dict = set.tags_dict()
	    for t in tags:
		if ((t) and (t not in tags_dict)):
		    n = Tag(set = set, tag = t)
		    n.put()
	    entry = SetEntry(set = set, question = question, answer = answer, tags = tags)
	    entry.put()
	    set.entryCount = set.entryCount + 1
	    set.deleteCache(all = True)
	    set.put()
	    key_as_str = str(entry.key())
	    return_data = {'status': 'ok', 'entryKey': key_as_str, 'question': question, 'answer': answer, 'tags': tags}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/getEntry.json'):
	    entryKey = self.request.get('entryKey')
	    entry = self.getCache(entryKey)
	    return_data = {}
	    tags_json = {}
	    if (not entry):
		return_data['tags'] = [];
		return_data['tags_json'] = {}
		return_data['q'] = 'This entry has been deleted!'
		return_data['a'] = 'This entry has been deleted!'
		return_data['deleted'] = True
		return_data = simplejson.dumps(return_data)
		self.response.headers['Content-Type'] = 'application/json'
		self.response.out.write(return_data)
		return;

	    for i in entry.tags:
		tags_json[i] = True
	    return_data['tags'] = entry.tags;
	    return_data['tags_json'] = tags_json
	    return_data['q'] = entry.question
	    return_data['a'] = entry.answer
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)

	    setKey = self.request.get('setKey')
	    view = self.request.get('view')
	    if (view == "study"):
		result = StudyResult.gql("WHERE user = :user AND entry = :entry", user = user, entry = entry).get();
		if (not result):
		    set = self.getCache(setKey)
		    entry = self.getCache(entryKey)
		    result = StudyResult(user = user, set = set, entry = entry)
		result.shown = int(self.request.get('shown'))
		result.wrong = int(self.request.get('wrong'))
		result.ratio = float(result.wrong / result.shown)
		result.put();
	    return
	elif (path == '/ajax/addTag.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    tag = self.request.get('tag')
	    tags_dict = set.tags_dict();
	    return_data = {}
	    return_data['status'] = 'exist'
	    if (tag not in tags_dict):
		n = Tag(set = set, tag = tag)
		n.put()
		return_data['status'] = 'added'
	    set.deleteCache(all = True)
	    tags = set.tags()
	    return_data['tags'] = tags;
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/deleteTag.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    tag = self.request.get('tag');
	    tag = Tag.gql("WHERE set = :set AND tag = :tag", set = set, tag = tag).get()
	    tag.delete()
	    set.deleteCache(all = True)
	    tags = set.tags()
	    tags.insert(0,'')
	    return_data = {}
	    return_data['tags'] = tags;
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/taglist.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    tags = set.tags()
	    tags.insert(0,'');
	    return_data = {}
	    return_data['tags'] = tags;
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return
	elif (path == '/ajax/listViewSearchResultTable.html'):
            setKey = self.request.get('setKey')
	    searchTerm = self.request.get('searchTerm')
	    tag = self.request.get('tag')
	    set = self.getCache(setKey)
	    entries = []
	    if (searchTerm != ''):
		seen = {}
		if (set.entryCount <= 1000):
		    resultSet = SetEntry.gql("WHERE set = :set", set = set)
		    for r in resultSet:
			key = str(r.key())
			if key not in seen:
			    seen[key] = True
			    q = r.question.lower()
			    a = r.answer.lower()
			    if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
				entries.append(r)
		else:
		    tags = set.tags()
		    for t in tags:
			resultSet = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = t)
			for r in resultSet:
			    key = str(r.key())
			    if key not in seen:
				seen[key] = True
				q = r.question.lower()
				a = r.answer.lower()
				if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
				    entries.append(r)
	    elif ((tag != '') and (tag != '-')):
		resultSet = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = tag)
		for r in resultSet:
		    entries.append(r)
	    else:
		resultSet = SetEntry.gql("WHERE set = :set", set = set)
		for r in resultSet:
		    entries.append(r)
	    template_values['entries'] = entries								    
	    template_values['setKey'] = setKey
	    template_values['entryCount'] = set.entryCount
	    template_values['contributor'] = set.isContributor(user)
	elif (path == '/ajax/listentries.html'):
            setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    tags = set.tags()
	    taglist = '<option value="">' + 'Please select a tag.' + '</option>';
	    for t in tags:
		taglist = taglist + '<option value="' + t + '">' + t + '</option>'
	    entries = []
	    template_values['taglist'] = taglist
	    template_values['entries'] = entries								    
	    template_values['setKey'] = setKey
	    template_values['entryCount'] = set.entryCount
	    template_values['contributor'] = set.isContributor(user)
	elif (path == '/ajax/entriesSearch.html'):
	    setKey = self.request.get('setKey')
	    entryKey = self.request.get('entryKey')
	    searchTerm = self.request.get('searchTerm').lower()
	    set = self.getCache(setKey)
	    entry = self.getCache(entryKey)
            resultSet = SetEntry.gql("WHERE set = :set", set = set.key())	    
	    entries = []
	    for r in resultSet:
		if (r == set):
		    continue
		q = r.question.lower()
		a = r.answer.lower()
		if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
		    entries.append(r)
	    template_values['entries'] = entries;								    
	    template_values['setKey'] = setKey;
	    template_values['contributor'] = set.isContributor(user)
        elif (path == '/ajax/editentry.html'):
	    pass
	elif (path == '/ajax/updateentry.json'):
	    entryKey = self.request.get('entryKey')
	    setKey = self.request.get('setKey')
	    question = self.request.get('question')
	    answer = self.request.get('answer')
	    tags = self.request.get_all('tags')
            if (question or answer):
		set = self.getCache(setKey)
		tags_dict = set.tags_dict()
		for t in tags:
		    if ((t) and (t not in tags_dict)):
			n = Tag(set = set, tag = t)
			n.put()
                entry = self.getCache(entryKey)
                entry.question = question
		entry.answer = answer
		entry.tags = tags
	        entry.put()			     
		set.deleteCache(all = True)
		self.deleteCache(str(entry.key()))
		return_data = { }
		return_data = simplejson.dumps(return_data)
		self.response.headers['Content-Type'] = 'application/json'
		self.response.out.write(return_data)
		return;
	elif (path == '/social/updateDisplayName.json'):
	    user.name = self.request.get('displayName')
	    user.put()
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/social/addUser.json'):
	    # self.get_current_user() called above already add the user.  Nothing to be done here
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	    if not self._isValidSignature():
		self.response.out.write({})
		return
	    # opensocial_owner_id, opensocial_viewer_id, xoauth_public_key, oauth_consumer_key, oauth_nonce, oauth_signature, oauth_signature_method, 
	    # oauth_timestamp, oauth_token, opensocial_instance_id, opensocial_app_id, opensocial_app_url
	    # http://wiki.opensocial.org/index.php?title=Introduction_To_Signed_Requests
	    ownerID = self.request.get('opensocial_owner_id')
	    viewerID = self.request.get('opensocial_viewer_id')
	    consumerKey = self.request.get('oauth_consumer_key')
	    signature = self.request.get('oauth_signature')
	    token = self.request.get('oauth_token')
	    sys.stderr.write('Owner ID: ' + ownerID)
	    sys.stderr.write('Viewer ID: ' + viewerID)
	    sys.stderr.write('Consumer Key: ' + consumerKey)
	    sys.stderr.write('signature: ' + signature)
	    sys.stderr.write('token:' + token)
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/deleteentry.json'):
            entryKey = self.request.get('entryKey')	    
	    entry = self.getCache(entryKey)
	    set = entry.set;
	    set.entryCount = set.entryCount - 1
	    set.put()
	    set.deleteCache(all = True)
	    self.deleteCache(entryKey)
	    entry.delete()
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/setSubscribe.html'):
            setKey = self.request.get('setKey')	    
	    set = self.getCache(setKey)
	    set.subscribe(user)
	    template_values['title'] = set.title				    
	elif (path == '/ajax/setUnsubscribe.html'):
            setKey = self.request.get('setKey')	    
	    confirmed = self.request.get('confirmed')
	    template_values['confirmed'] = confirmed
	    template_values['setKey'] = setKey
	    set = self.getCache(setKey)
	    template_values['title'] = set.title
	    if (confirmed == '1'):
		set.unsubscribe(user)
	elif (path == '/ajax/usageHistory.html'):
	    authored = {}
	    currently_subscribed = {}
	    sets = StudySet.gql("WHERE author = :author ORDER BY title", author = self.get_current_user())
	    for s in sets:
		authored[str(s.key())] = True

	    sets = SetCurrentSubscription.gql("WHERE user = :user", user = user)
	    for s in sets:
		currently_subscribed[str(s.set.key())] = True

	    subscriptions = SetSubscription.gql("WHERE user = :user",user = user)

	    subscriptions_t = [];
	    seen = {}
	    for s in subscriptions:
		key_as_str = str(s.set.key())
		if (key_as_str not in seen):
		    if (key_as_str in authored):
			s.set.authored = True
		    if (key_as_str in currently_subscribed):
			s.set.current = True
		    subscriptions_t.append(s.set)
		    seen[key_as_str] = True
            template_values['subscriptions'] = subscriptions_t;
	elif (path == '/ajax/search.html'):
	    authored = {}
	    currently_subscribed = {}
	    searchTerm = self.request.get('search')
	    sets = StudySet.gql("WHERE author = :author ORDER BY title", author = self.get_current_user())
	    for s in sets:
		authored[str(s.key())] = True

	    sets = SetCurrentSubscription.gql("WHERE user = :user", user = user)
	    for s in sets:
		currently_subscribed[str(s.set.key())] = True

	    sets = StudySet.gql("WHERE visibility = 'public' ORDER BY author")
	    results = []
	    for set in sets:
		title = set.title.lower();
		description = set.description.lower()
		searchTerm = searchTerm.lower()
		if (title.find(searchTerm) > -1) or (description.find(searchTerm) > -1):
		    data = {}
		    data['title'] = set.title
		    data['description'] = set.description
		    data['author'] = set.author.getName()
		    key_as_str = str(set.key())
		    if (key_as_str in authored):
			data['authored'] = True
		    if (key_as_str in currently_subscribed):
			data['current'] = True
		    if ((set.subscriptionCount is None) or (set.entryCount is None)):
			set.subscriptionCount = SetCurrentSubscription.gql("WHERE set = :set", set = set).count()
			set.entryCount = SetEntry.gql("WHERE set = :set", set = set).count()
			set.deleteCache(all = False);
			set.put()
		    data['subscriptionCount'] = set.subscriptionCount
		    data['entryCount'] = set.entryCount
		    data['key'] = key_as_str
		    results.append(data);
            template_values['results'] = results;
	elif (path == '/ajax/getsetlist.json'):
	    return_data = {}
	    preferences = Preferences.gql("WHERE user = :user", user = user).get()
	    current_set_key = ''
	    if (preferences.current_set is not None):
		current_set_key = str(preferences.current_set.key())
		# We are including the renderSetHTML and the tags list so that browser does not have to do another request
		setKey = current_set_key
		set = self.getCache(setKey)
		template_values['title'] = set.title
		template_values['setKey'] = setKey
		currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = set).get();
		template_values['subscribed'] = False
		template_values['contributor'] = set.isContributor(user)
		if currentSubscription:
		    template_values['subscribed'] = True						      
		template_file = os.path.join(os.path.dirname(__file__), 'templates/ajax/renderSet.html')
		return_data['renderSetHTML'] = template.render(template_file, template_values)
		tags = set.tags()
		tags.insert(0,'');
		return_data['tags'] = tags;
	    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user", user = user)
	    cs_sets = []
	    userDisplayName = user.getName()
	    userEmail = user.getEmail()
	    for cs in currentSubscription:
		str_key = str(cs.set.key())
		title = cs.set.title
		current = 0
		if (str_key == current_set_key):
		    current = 1
		cs_sets.append([str_key,title,current])
		if ((cs.userDisplayName != userDisplayName) or (cs.userEmail != userEmail)):
		    cs.userDisplayName = userDisplayName
		    cs.userEmail = userEmail
		    cs.put()
	    cs_sets.sort(sort_setlist)
	    return_data['list'] = cs_sets
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/removeContributors.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    contributors = []
	    selected = self.request.get_all('contributor')
	    if (self._isValidSignature()):
		selected = selected.pop(0);
		selected = selected.split(',')
	    for c in selected:
		contributor = self.getCache(c)
		contributors.append(contributor)
	    set.removeContributors(user,contributors)
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/addContributors.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    contributors = []
	    selected = self.request.get_all('contributor')
	    if (self._isValidSignature()):
		selected = selected.pop(0);
		selected = selected.split(',')
	    for c in selected:
		contributor = self.getCache(c)
		contributors.append(contributor)
	    set.addContributors(user,contributors)
	    return_data = { }
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/contributorSearch.json'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    nameOrEmail = self.request.get('nameOrEmail')
	    results = SetCurrentSubscription.gql("WHERE set = :set AND userDisplayName = :userDisplayName", set = set, userDisplayName = nameOrEmail)
	    found = []
	    seen = {}
	    for r in results:
		t = {};
		if (r.user == user):
		    continue
		k = t['key'] = str(r.user.key())
		seen[k] = True
		t['displayName'] = r.user.getName()
		found.append(t)
	    results = SetCurrentSubscription.gql("WHERE set = :set AND userEmail = :userEmail", set = set, userEmail = nameOrEmail)
	    for r in results:
		t = {};
		if (r.user == user):
		    continue
		k = str(r.user.key())
		if (k not in seen):
		    t['key'] = k
		    t['displayName'] = r.user.getName() or nameOrEmail
		    found.append(t)
	    return_data = {'users': found}
	    return_data = simplejson.dumps(return_data)
	    self.response.headers['Content-Type'] = 'application/json'
	    self.response.out.write(return_data)
	    return;
	elif (path == '/ajax/contributors.html'):
	    # list the current contributors so that current contributor can remove themselve if they no longer contributing to the 
	    # set.
	    # Display an interface so that contributors can search existing user by name
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    contrib = set.getContributors()
	    contributors = []
	    seen = {}
	    for c in contrib:
		t = {}
		k = str(c.key())
		if (k not in seen):
		    t['key'] = str(c.key())
		    t['name'] = c.getName()
		    t['email'] = c.getEmail()
		    if (self.host == 'kdoan.dyndns.org'):
			t['debug'] = True
		    contributors.append(t)
		    seen[k] = True
	    template_values['contributors'] = contributors
	elif (path == '/ajax/sharing.html'):
	    setKey = self.request.get('setKey')
	    set = self.getCache(setKey)
	    template_values['contributor'] = set.isContributor(user)
	    if ((user.getEmail() is None) or (user.getEmail() == '')):
		template_values['noFeedbackWarning'] = True
	elif (path == '/share/set.html'):
	    if (self._isValidSignature()):
		setKey = self.request.get('setKey')
		set = self.getCache(setKey)
		set.subscribe(user)
		set.makeThisSetCurrentForUser(user)
	    elif (not user):
		template_values['loginURL'] = users.create_login_url(self.request.uri)
	    else:
		setKey = self.request.get('setKey')
		set = self.getCache(setKey)
		set.subscribe(user)
		set.makeThisSetCurrentForUser(user)
		self.redirect(self.baseURL)
	elif (path == '/dynamic/javascript_nosocial.js'):
	    return;
	elif (path == '/dynamic/javascript.js'):
	    base = os.path.dirname(__file__)
	    js = file(os.path.join(base,'javascript/jquery-1.2.6.min.js')).read();
	    js = js + file(os.path.join(base,'javascript/jquery.ui-1.5.2/ui/minified/jquery.ui.all.min.js')).read() + "\n\n"
	    js = js + file(os.path.join(base,'javascript/plugins4jquery/blockUI.js')).read() + "\n\n"
	    js = js + file(os.path.join(base,'javascript/jquery.scrollIntoView.js')).read() + "\n\n"
	    js = js + file(os.path.join(base,'javascript/our.js')).read() + "\n\n"
	    js = js + 'study.templates = ' + self.combineTemplates() + ';' + "\n\n"
	    js = js + template.render(os.path.join(base,'javascript/social.js'),template_values) + "\n\n"
	    #js = jsmin.jsmin(js)
	    self.response.headers['Content-Type'] = 'text/javascript'
	    self.response.out.write(js)
	    return;
	elif (path == '/dynamic/social.xml'):
	    base = os.path.dirname(__file__)
	    css = template.render(os.path.join(base,'css/themes/flora/flora.tabs.css'),{})
	    css = css + "\n" + template.render(os.path.join(base,'css/themes/flora/flora.css'),{})
	    template_values['css'] = css
	    result = template.render(os.path.join(base, 'dynamic/social.xml'), template_values)
	    self.response.out.write(result)
	    return;

        template_file = os.path.join(os.path.dirname(__file__), template_file)
	self.response.out.write(template.render(template_file, template_values))

class CustomUser(db.Model):
    isSocial = db.BooleanProperty(default = False)
    viewerID = db.StringProperty(default = '')
    consumerKey = db.StringProperty(default = '')
    email = db.StringProperty(default = '')
    name = db.StringProperty(default = '')
    googleAccount = db.UserProperty()
    def getEmail(self):
	if (self.email) and (self.email != ''):
	    return self.email
	elif (self.googleAccount) and (self.googleAccount != ''):
	    return self.googleAccount.email()
    def getName(self):
	if (self.name) and (self.name != ''):
	    return self.name
	elif (self.googleAccount) and (self.googleAccount != ''):
	    if (self.googleAccount.nickname() == self.googleAccount.email()):
		return ""
	    return self.googleAccount.nickname()

class StudySet(db.Model):
    author = db.ReferenceProperty(CustomUser)
    title = db.StringProperty()
    visibility = db.StringProperty()
    date = db.DateTimeProperty(auto_now_add=True)
    studyMaterials = db.TextProperty()
    description = db.StringProperty()
    entryCount = db.IntegerProperty()
    subscriptionCount = db.IntegerProperty();
    timestamp = db.DateTimeProperty(auto_now=True)
    def tags_dict(self):
	result = {}
	tags = Tag.gql("WHERE set = :set", set = self)
	for t in tags:
	    result[t.tag] = True;
	return result
    def tags(self):
	ck = str(self.key()) + '_tags'
	tags = memcache.get(ck)
	if tags is not None:
	    return simplejson.loads(tags)
	result = []
	tags = Tag.gql("WHERE set = :set ORDER BY tag ASC", set = self)
	for t in tags:
	    result.append(t.tag)
	memcache.set(ck,simplejson.dumps(result))
	return result
    def deleteCache(self,all):
	if (not all):
	    memcache.delete(str(self.key()))
	    return;
	tags = self.tags()
	tags.append('_tags')
	tags.append('')
	memcache.delete_multi(tags, key_prefix=str(self.key()))
    def isOwner(self,user):
	if (self.author.key() == user.key()):
	    return True
	if (self.author == user):
	    return True
    def isContributor(self,user):
	if (self.isOwner(user)):
	    return True
	results = Contributor.gql("WHERE set = :set AND user = :user", set = self, user = user).get()
	if (results):
	    return True
	return False
    def removeContributors(self,user,contributors):
	if (self.isContributor(user)):
	    for c in contributors:
		if (c == user):
		    continue
		r = Contributor.gql("WHERE set = :set AND user = :user", set = self, user = c).fetch(1000)
		db.delete(r)
    def addContributors(self,user,contributors):
	if (self.isContributor(user)):
	    for c in contributors:
		exist = Contributor.gql("WHERE set = :set AND user = :user", set = self, user = c).get()
		if (exist is None):
		    r = Contributor(set = self, user = c)
		    r.put()
    def getContributors(self):
	contributors = []
	results = Contributor.gql("WHERE set = :set", set = self);
	for r in results:
	    contributors.append(r.user)
	contributors.append(self.author)
	return contributors
    def getContributorEmails(self):
	emails = []
	contributors = self.getContributors()
	for c in contributors:
	    email = c.getEmail()
	    if ((email is not None) and (email != '')):
		emails.append(email)
	if (len(emails)):
	    return emails
    def isEditable(self,user):
	return self.isContributor(user)
    def isViewable(self,user):
	if (self.isOwner(user)):
	    return True
	elif (visibility == 'public'):
	    return True
	elif (visibility == 'private'):
	    return False
	elif (visibility == 'invited'):
	    # @todo Need logic to know if this user was invited to study / contribute to this set
	    return False
	return False
    def subscribe(self,user):
        subscription = SetSubscription.gql("WHERE use = :user and set = :set", user = user, set = self).get()
	if (not subscription):
	    subscription = SetSubscription(user = user, set = self)
	    subscription.put()
	currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = self).get();
	if (not currentSubscription):
	    currentSubscription = SetCurrentSubscription(user = user, set = self, userDisplayName = user.getName(), userEmail = user.getEmail())
	    currentSubscription.put()
	    self.subscriptionCount = self.subscriptionCount + 1
	    self.put()
	    self.deleteCache(all = False)
    def unsubscribe(self,user):
	currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = self).get();
	if (currentSubscription):
	    currentSubscription.delete()
	    self.subscriptionCount = self.subscriptionCount - 1
	    self.put()
	    self.deleteCache(all = False)
    def makeThisSetCurrentForUser(self,user):
	preferences = Preferences.gql("WHERE user = :user", user = user).get()
	preferences.current_set = self
	preferences.put()

class Contributor(db.Model):
    set = db.ReferenceProperty(StudySet)
    user = db.ReferenceProperty(CustomUser)
    timestamp = db.DateTimeProperty(auto_now=True)

class SetEntry(db.Model):
    set = db.ReferenceProperty(StudySet, collection_name = 'entries')
    question = db.TextProperty()
    answer = db.TextProperty()
    tags = db.StringListProperty()
    timestamp = db.DateTimeProperty(auto_now=True)

class SetSubscription(db.Model):
    user = db.ReferenceProperty(CustomUser)
    set = db.ReferenceProperty(StudySet)
    date_subscribed = db.DateTimeProperty(auto_now_add = True)					
    timestamp = db.DateTimeProperty(auto_now=True)

class SetCurrentSubscription(db.Model):
    user = db.ReferenceProperty(CustomUser)
    set = db.ReferenceProperty(StudySet)
    timestamp = db.DateTimeProperty(auto_now=True)
    userDisplayName = db.StringProperty(default = '')
    userEmail = db.StringProperty(default = '')

class StudyResult(db.Model):
    user = db.ReferenceProperty(CustomUser)
    set = db.ReferenceProperty(StudySet)
    entry = db.ReferenceProperty(SetEntry)
    lastDisplayedTime = db.DateTimeProperty(auto_now=True)
    firstDisplayedTime = db.DateProperty(auto_now_add=True)
    shown = db.IntegerProperty()
    wrong = db.IntegerProperty()
    ratio = db.FloatProperty()

class Tag(db.Model):
    set = db.ReferenceProperty(StudySet)
    tag = db.StringProperty()
    timestamp = db.DateTimeProperty(auto_now=True)

class Preferences(db.Model):
    user = db.ReferenceProperty(CustomUser)
    current_set = db.ReferenceProperty(StudySet)
    date_joined = db.DateTimeProperty(auto_now_add = True)
    lastLoginTime = db.DateTimeProperty(auto_now=True)

def real_main():
    application = webapp.WSGIApplication(
	[('/.*',Page)],
        debug = True)
    #wsgiref.handlers.CGIHandler().run(application)
    run_wsgi_app(application)

def profile_main():
    import cProfile, pstats, StringIO
    prof = cProfile.Profile()
    prof = prof.runctx("real_main()", globals(), locals())
    stream = StringIO.StringIO()
    stats = pstats.Stats(prof, stream=stream)
    stats.sort_stats("time")
    stats.print_stats(80)
    logging.info("Profile data:\n%s", stream.getvalue())

main = real_main
if __name__ == "__main__":
    main()
